#!/usr/bin/perl -w #-d:ptkdb
# (c) 2009-2010 Tom Deblauwe
# License: GPL V3

use strict;
use File::Temp qw( :POSIX );
use lib "../lib";
use Class::Struct;
#   make KEY_BTAB (shift-tab) working in XTerm
#   and also at the same time enable colors
#$ENV{TERM} = "xterm-vt220" if ($ENV{TERM} eq 'xterm');

use FindBin;
use lib "$FindBin::RealBin/../lib";
use Curses;
use Curses::UI;
use Cwd 'abs_path';
use Time::HiRes qw( usleep );
use threads;
use threads::shared;

use Devel::GDB;

sub atoi {
        my $t = 0;
        foreach my $d (split(//, shift())) {
            $t = $t * 10 + $d;
        }
        return $t;
        }




#dbg code
my $debug = 0;
my $g_local_debug = 0;

#program arguments
my $g_attach_pid = 0;
my $g_executable;
my $g_coredump_dir; #dirname (we get the most recent out of this dirlisting) or filename
my $g_executable_dir;
my $g_executable_src_dir;

use vars qw($g_attach_pid $g_local_debug $g_executable $g_coredump_dir $g_executable_dir $g_executable_src_dir);

my $option = shift;
while( defined($option) )
{
    if($option eq "-dbg")
    {
    my $fh = tmpfile();
    open STDERR, ">dbg.txt";
    $debug = 1;
    }
    if($option eq "-beer")
    {
    $g_local_debug = 1;
    }
    if($option eq "-a")
    {
    $option = shift;
    if(defined($option))
    {
        $g_attach_pid = atoi($option);
    }
    }
    if($option eq "-c")
    {
    $option = shift;
    if(defined($option))
    {
        $g_coredump_dir = $option;
    }
    }
    if($option eq "-x" || $option eq "-p")
    {
    $option = shift;
    if(defined($option))
    {
        $g_executable = $option;
    }
    }

    if($option eq "-d")
    {
    $option = shift;
    if(defined($option))
    {
        $g_executable_dir = $option;
    }
    }

    if($option eq "-s")
    {
    $option = shift;
    if(defined($option))
    {
        $g_executable_src_dir = $option;
    }
    }

    $option = shift;
}

sub validateParams()
{
    if(!defined($g_executable))
    {
        print "Please provide a valid executable using the -p option!\r\n";
        return 0;
    }

    if($g_attach_pid != 0)
    {
        return 1;
    }
    else
    {
        my $ok = 1;
        if(!defined($g_coredump_dir))
        {
        print "Please provide a coredump using the -c option!\r\n";
        $ok = 0;
        }
        return $ok;
    }
    return 0;
}

sub printUsage()
{
print "Usage for emgdb:\r\n";
print "\t 1. Examine coredump: emgdb -p PROCESSEXECUTABLE -c COREDUMPFILE\r\n";
print "\t 2. Attach to process: emgdb -p PROCESSEXECUTABLE -a PROCESSID\r\n";
}

if(validateParams() == 0)
{
printUsage();
exit(1);
}

if($debug == 0)
{
    # We do not want STDERR to clutter our screen.
    my $fh = tmpfile();
    open STDERR, ">/dev/null";
}

my %w = ();

# Create the root object.
my $cui = new Curses::UI (
    -clear_on_exit => 1,
    -debug => $debug,
);

sub dbg
{
    if($debug)
    {
        print STDERR $_[0] . "\n";
    }
    if(defined($w{20}))
    {
        my $label = $w{20}->getobj('debuglabel');
        $label->text($label->get . $_[0] . "\n");
    }
}

# ----------------------------------------------------------------------
# Util functions
# ----------------------------------------------------------------------
sub trim($)
{
        my $string = shift;
        $string =~ s/^\s+//;
        $string =~ s/\s+$//;
        return $string;
}

sub doStatus
{
    my $status = shift;
    $cui->status($status);
}

sub doMessage
{
    my $msg = shift;
    dbg $msg;
    $cui->dialog(
        -message => $msg,
            -buttons => ['ok'],
            -title   => 'Info',
    );
}

sub doConfirm
{
    my $msg = shift;
    my $isok = $cui->dialog(
        -message => $msg,
            -buttons => ['no', 'yes'],
            -title   => 'Question',
    );
    return $isok;
}

sub hideStatus
{
    $cui->nostatus;
}
sub runCmd
{
    my $cmd = shift;
    my $status = shift;
    if(defined($status) && length($status) != 0)
    {
        $status .= "\nExecuting: $cmd";
    }
    else
    {
        $status = "Executing: $cmd";
    }
    doStatus($status);
    dbg("Cmd: " . $cmd);
    unless(open(PROGRAM, $cmd . " 2>&1|"))
    {
        dbg("Failed: open, err=$!");
        doStatus("Failed:" . $cmd);
        return "";
    }
    my $bytesread;
    my $buffer;
    my $line = "";
    my $alloutput = "";
    my $progress = 0;
    while ($bytesread = read(PROGRAM, $buffer, 1))
    {
        $line .= $buffer;
        if($buffer eq "\n")
        {
            if($progress == 0)
            {
                dbg "line:" . trim($line);
            }
            else
            {
                $cui->noprogress;
                $progress = 0;
                doStatus($status);
            }
            $alloutput .= trim($line) . "\n";
            $line = "";
        }
        else
        {
            if($line =~ /.*[\s|\x08](\d+)\/([0-9]+)\x08/)
            {
                if($progress == 0)
                {
                    $cui->progress(
                        -min => 0,
                        -max => $2,
                        -title => 'Progress of command',
                        -message => $cmd,
                    );
                    $progress = 1;
                }
                $cui->setprogress($1);
                $line = "";
            }
        }
    }
    if($line ne "")
    {
        $alloutput .= trim($line);
    }
    close(PROGRAM);
    if($progress == 1)
    {
        $cui->noprogress;
    }
    hideStatus();
    return trim($alloutput);
}

sub copyFile
{
    my $src = shift;
    my $dest = shift;
        runCmd("cp -a -R -v $src $dest", "Copying file(s)...");
}

sub removeFile
{
    my $src = shift;
        runCmd("rm -v $src", "Removing file(s)...");
}

sub convertToHex
{
    my $iNum = shift;
    my $tmp = sprintf("%d", $iNum);
    my $hexprint = hex($tmp);
    return hex($tmp);
}

sub getVersion
{
    my $input = shift;
    my $result = 0;
    if($input =~ /.*([0-9]{2})[-|\,|\.]([0-9]{2})[-|\,|\.]([0-9]{2})[-|\,|\.]([0-9]{2,4}).*/)
    {
        $result |= (convertToHex($1) << 24);
        $result |= (convertToHex($2) << 16);
        $result |= (convertToHex($3) << 8);
        $result |= convertToHex($4);
    }
    else
    {
        if($input =~ /.*([0-9]{2})[-|\,|\.]([0-9]{2})[-|\,|\.]([0-9]{2,4}).*/)
        {
            $result |= (convertToHex($1) << 24);
            $result |= (convertToHex($2) << 16);
            $result |= (convertToHex($3) << 8);
        }
    }
    return $result;
}

# ----------------------------------------------------------------------
# Global vars
# ----------------------------------------------------------------------
my $g_gdb;
my $g_original_dir;
my $g_coredump;
my $g_gdb_state = 0;
my $g_gdb_thread;
my $g_gdb_thread_command;
my $g_gdb_thread_result;

share($g_gdb_state);
share($g_gdb_thread_command);
share($g_gdb_thread_result);

use vars qw($g_coredump $g_gdb $g_original_dir $g_gdb_state $g_gdb_thread $g_gdb_thread_command $g_gdb_thread_result);

# ----------------------------------------------------------------------
# GDB functions
# ----------------------------------------------------------------------
sub sendCommandThread
{
my $gdb = shift;
dbg("starting task");
while($g_gdb_state >= 0)
{
    if($g_gdb_state == 1)
    {
#      new command arrived
    my $command = $g_gdb_thread_command;
    dbg("threadcmd=" . $command);
    $g_gdb_thread_result = $gdb->get($command);
    dbg("threadresult:$g_gdb_thread_result");
    $g_gdb_state = 0;
    }
    else
    {
    usleep(50000);
    }
}
dbg("ending task");
}

sub sendCommand;

sub newDebugger
{
    my $executable = shift;
    my $coredump = shift;
    my $src = shift;
    my $gdb = new Devel::GDB('-params' => "-q $executable $coredump");
    $gdb->get("set print pretty on");
    $gdb->get("set print vtbl on");
    $gdb->get("set print object on");
    if(defined($src))
    {
    if(-e "$src/dirlist.txt")
    {
        my $dirlist = qx/cat $src\/dirlist.txt/;
        my @parselist = split(/:/, $dirlist);
        foreach my $dir (@parselist)
        {
            $dir = trim($dir);
            if(length($dir) > 0)
            {
            $gdb->get("directory $src/$dir");
            }
        }
    }
    $gdb->get("directory $src");
    }
#     get initial messages
    $gdb->get("");

    $g_gdb_state = 0; #breaked
    $g_gdb_thread = threads->create('sendCommandThread', $gdb);
    dbg("Debugger communication thread is created");

    return $gdb;
}

sub deleteDebugger
{
    my $gdb = shift;
    $g_gdb_state = -1;
    if($g_gdb_thread->is_joinable())
    {
    $g_gdb_thread->join();
    }
    $gdb->end;
}

sub sendCommand
{
    my $gdb = shift;
    my $command = shift;
    my $couldblock = shift;
    dbg("sending to gdb: $command");
#     my $e = $gdb->get_expect_obj;

    if($g_gdb_state == 1)
    {
        #command is executing, can't do a thing
        return "";
    }

    if((defined($couldblock) && $couldblock == 1) || $g_attach_pid != 0) #live debugging
    {
    dbg("Send threaded");
    if($g_gdb_state == 0)
    {
        $g_gdb_thread_result = "";
        $g_gdb_thread_command = $command;
        $g_gdb_state = 1; #ok, thread can do the job

        my $wait = 300;
        while($g_gdb_state == 1 && $wait > 0)
        {
            $wait -= 1;
            usleep(10000);
        }
        if($wait <= 0)
        {
#           command took more than 3 seconds
            dbg("Command took more than 3 seconds");
            return "TIMEOUT";
        }
        if($g_gdb_state == 0)
        {
            dbg("Command is finished:$g_gdb_thread_result");
            return $g_gdb_thread_result;
        }
    }
    return "";
    }
    else
    {
    my $res = $gdb->get($command);
    dbg("result:$res");
    return $res;
    }
#     $gdb->send_cmd($command);
#     $e->expect(5);
#     my $ret = $e->before();
}

# ----------------------------------------------------------------------
# GUI PART
# ----------------------------------------------------------------------
# Start page
my $current_config = 30;

sub selectConfig($;)
{
    my $nr = shift;
    $current_config = $nr;
    $w{$current_config}->focus;
}

# Screens
my %screens = (
    '20'  => 'Debug output',
    '30'  => 'Welcome',
);

# ----------------------------------------------------------------------
# Create a menu
# ----------------------------------------------------------------------
my $info_menu = [
    { -label => 'Debug',                -value => sub{selectConfig(20)}  },
];

my $menu = [
    { -label => 'Info',        -submenu => $info_menu         },
];

$cui->add('menu', 'Menubar', -menu => $menu);

# ----------------------------------------------------------------------
# Add the screens
# ----------------------------------------------------------------------
my @screens = sort {$a<=>$b} keys %screens;
my %args = (
    -border       => 1,
    -titlereverse => 0,
    -padtop       => 0,
    -padbottom    => 0,
    -ipad         => 0,
);

while (my ($nr, $title) = each %screens)
{
    my $id = "window_$nr";
    $w{$nr} = $cui->add(
        $id, 'Window',
        -title => "EmGDB debugger: $title",
        %args
    );
}

# ----------------------------------------------------------------------
# Screen: Debug output
# ----------------------------------------------------------------------
sub showMainmenu;

$w{20}->add(
    undef, 'Buttonbox',
    -border => 1,
    -buttons => [
        {
            -label => "< Main menu >",
            -onpress => \&showMainmenu,
        },
    ],
);

$w{20}->add(
    'debuglabel', 'TextViewer',
    -text => "",
    -border => 1,
    -width => -1,
    -height => -1,
    -y => 3,

);

# ----------------------------------------------------------------------
# Screen: Threads
# ----------------------------------------------------------------------
sub doQuit;
sub showStackTrace;
sub cbThreadSelected;
sub cbFrameSelected;
sub cbDbgBreak;
sub cbDbgContinue;
sub cbDbgStep;
sub cbRefreshAllInfo;

$w{30}->add(
    'menubox', 'Buttonbox',
    -y => 0,
    -vertical => 1,
    -border => 1,
    -buttons => [
{
            -label => "< Quit >",
            -onpress => \&doQuit,
},
    ],
);

$w{30}->add(
    'threadlist', 'Listbox',
    -y          => 3,
    -padbottom  => 2,
    -padright   => 15,
    -width      => -1,
    -height     => 10,
    -border     => 1,
    -title      => 'Threads',
    -vscrollbar => 1,
    -hscrollbar => 1,
    -onchange => \&cbThreadSelected,
);

$w{30}->add(
    'currentdbgstatus', 'TextViewer',
    -title => 'Status',
    -text => "",
    -padbottom => 0,
    -border => 1,
    -width => 15,
    -height => 1,
    -x => -1,
    -y => 3
);

$w{30}->add(
    'stacklist', 'Listbox',
    -y          => 11,
    -padbottom  => 2,
    -width      => -1,
    -height     => 16,
    -border     => 1,
    -title      => 'Stack',
    -vscrollbar => 1,
    -hscrollbar => 1,
    -onchange => \&cbFrameSelected,
);

$w{30}->add(
    'framelist', 'Listbox',
    -y          => 25,
    -padbottom  => 0,
    -width      => -1,
    -height     => 10,
    -border     => 1,
    -title      => 'Variables',
    -vscrollbar => 1,
    -hscrollbar => 1,
);

if($g_attach_pid == 0)
{
    $w{30}->add(
        'commandline', 'TextEntry',
        -title => 'Command',
        -y => 35,
        -width => 50,
        -border => 1,
    );

$w{30}->add(
    'currentsourceline', 'TextViewer',
    -title => 'Source',
    -text => "",
    -padbottom => 0,
    -border => 1,
    -width => -1,
    -height => 1,
    -y => 35,
    -x => 50,
);

}
else
{
    $w{30}->add(
        'commandline', 'TextEntry',
        -title => 'Command',
        -y => 35,
        -width => 30,
        -border => 1,
    );

$w{30}->add(
    'currentsourceline', 'TextViewer',
    -title => 'Source',
    -text => "",
    -padbottom => 0,
    -border => 1,
    -width => 50,
    -height => 1,
    -y => 35,
    -x => 30,
);

    $w{30}->add(
        'dbgbox', 'Buttonbox',
        -y => 35,
        -x => 80,
        -horizontal => 1,
        -width => -1,
        -border => 1,
        -buttons => [
    {
                -label => "< Continue >",
                -onpress => \&cbDbgContinue,
    },
    {
                -label => "< Break >",
                -onpress => \&cbDbgBreak,
    },
    {
                -label => "< Step >",
                -onpress => \&cbDbgStep,
    },
    {
                -label => "< Refresh >",
                -onpress => \&cbRefreshAllInfo,
    },
        ],
    );

}

$w{30}->add(
    'commandoutputlabel', 'TextViewer',
    -text => "",
    -padbottom => 0,
    -border => 1,
    -width => -1,
    -height => -1,
    -y => 38,

);

# ----------------------------------------------------------------------
# Screen functions: Debug output
# ----------------------------------------------------------------------
sub showMainmenu
{
    selectConfig(30);
    $w{30}->getobj("threadlist")->focus;
}

# ----------------------------------------------------------------------
# Screen functions: Threads
# ----------------------------------------------------------------------
sub doSetKeybindingsOfList
{
    my $list = shift;
    $list->set_binding( sub { my $this = shift; $this->do_routine('option-next'); $this->do_routine('option-select'); }, KEY_DOWN);
    $list->set_binding( sub { my $this = shift; $this->do_routine('option-nextpage'); $this->do_routine('option-select'); }, KEY_NPAGE);
    $list->set_binding( sub { my $this = shift; $this->do_routine('option-prev'); $this->do_routine('option-select'); }, KEY_UP);
    $list->set_binding( sub { my $this = shift; $this->do_routine('option-prevpage'); $this->do_routine('option-select'); }, KEY_PPAGE);
}

sub cbThreadSelected
{
    my $this = shift;
    my $highlight = $this->get_active_value;
    if(defined($highlight) && length($highlight) > 0)
    {
        showStackTrace($highlight, $w{30}->getobj('stacklist'));
        $w{30}->getobj('stacklist')->draw(1);
    }
}

sub showStackTrace
{
    my $task = shift;
    dbg("show trace of task $task");
    my $list = shift;
    my @partlistvalues;
    my %partlistlabels;

    sendCommand($g_gdb, "t $task"); #switch to task
    my $stack = sendCommand($g_gdb, "bt"); #show backtrace
    my @stacklist = split(/\n/, $stack);
    my $size = length(@stacklist);
    my $laststacknum = 0;
    my $laststacknumindent = "";
    foreach my $stackline (@stacklist)
    {
        if($stackline =~ /^#(\d+)\s.*/)
        {
            $laststacknum = $1;
            $laststacknumindent = "";
        }
        else
        {
        $laststacknumindent .= " ";
        }
        {
            push(@partlistvalues, $laststacknumindent . $laststacknum);
            $partlistlabels{$laststacknumindent . $laststacknum} = $stackline;
            dbg("found stackline: '$laststacknumindent$laststacknum' => $stackline");
        }
#         else
#         {
#             dbg("ERR:was not a stack message:$stackline");
#         }
    }
    $list->values(\@partlistvalues);
    $list->labels(\%partlistlabels);
}

sub fillThreadList
{
    my $list = shift;
    my @partlistvalues;
    my %partlistlabels;
    my $threads = sendCommand($g_gdb, "info threads");
    dbg("Got threads:");
    my @threadslist = split(/\n/, $threads);
    my $size = length(@threadslist);
    foreach my $thr (@threadslist)
    {
        if($g_attach_pid == 0)
        {
            if($thr =~ /.*\s(\d+)\s(Thread|process)\s(\d+)\s+(.*)/)
            {
            push(@partlistvalues, $1);
            $partlistlabels{$1} = "$1 - ($3) - $4";
            dbg("found thread: $1 - $3 - $4");
            }
            else
        {
            dbg("ERR:was not a thread message:$thr");
            }
        }
        else
        {
            if($thr =~ /.*\s(\d+)\s(Thread|process).+LWP\s(\d+).*\sin\s+(.*)/)
            {
            push(@partlistvalues, $1);
            $partlistlabels{$1} = "$1 - ($3) - $4";
            dbg("found thread: $1 - $3 - $4");
            }
            else
            {
                if($thr =~ /.*\s(\d+)\s(Thread|process).+\s(in|at)\s+(.*)/)
                {
                    push(@partlistvalues, $1);
                    $partlistlabels{$1} = "$1 - $4";
                    dbg("found thread: $1 - $4");
                }
                else
                {
                    dbg("ERR:was not a thread message:$thr");
                }
            }
        }
    }
#   reverse list
    @partlistvalues = reverse(@partlistvalues);
    $list->values(\@partlistvalues);
    $list->labels(\%partlistlabels);
#   we select the last item, is where it crashed
    $list->set_selection(0);
}

sub doRefreshThreadList
{
fillThreadList($w{30}->getobj("threadlist"));
}

sub cbFrameSelected
{
    my $this = shift;
    my $highlight = $this->get_active_value;
    if(defined($highlight) && length($highlight) > 0)
    {
        showFrameTrace($highlight, $w{30}->getobj('framelist'));
        $w{30}->getobj('framelist')->draw(1);
    }
}

sub showFrameTrace
{
    my $frame = trim(shift);
    dbg("show trace of frame $frame");
    my $list = shift;
    my @partlistvalues;
    my %partlistlabels;

    my $sourceline = sendCommand($g_gdb, "frame $frame"); #switch to frame
#     dbg("srclinetrim=" . trim($sourceline) . "END");
#     dbg("srcline=" . $sourceline . "END");
    $sourceline = trim($sourceline);
    my @sourcelines = split(/\n/, $sourceline);
#     dbg("srclineslen=" . @sourcelines);
#     foreach my $t ( @sourcelines ) { dbg ("line=$t"); }
    if(@sourcelines >= 2)
    {
        my $line = $sourcelines[@sourcelines - 1];
        if( $line =~ /^(\d+)\s+(.*)/)
        {
            $w{30}->getobj('currentsourceline')->text($1 . " " . $2);
        }
    }
    else
    {
        $w{30}->getobj('currentsourceline')->text("");
    }
    my $stack = sendCommand($g_gdb, "info locals"); #show info
    my @stacklist = split(/\n/, $stack);
    my $size = length(@stacklist);
    my $linenum = 0;
    foreach my $stackline (@stacklist)
    {

        if($stackline ne ", ")
        {
            push(@partlistvalues, $linenum);
            $partlistlabels{$linenum} = $stackline;
            $linenum += 1;
            dbg("found frameline: '$stackline'");
        }
#         else
#         {
#             dbg("ERR:was not a stack message:$stackline");
#         }
    }
    $list->values(\@partlistvalues);
    $list->labels(\%partlistlabels);
}

sub cbRefreshAllInfo
{
    doRefreshThreadList;
    cbFrameSelected($w{30}->getobj('framelist'));
}

sub doExecuteCommand
{
    my $text = shift;
    my $output = shift;
    if(!defined($text) || !defined($output))
    {
        dbg("doExecuteCommand: Wrong arguments!");
        return;
    }
    my $command = $text->get;
    my $commandresult = sendCommand($g_gdb, $command);
    dbg("result=$commandresult");
    if(length($commandresult) == 0)
    {
        $commandresult = "<no output for command>";
    }
    $output->text($commandresult);
}

sub cbExecuteCommand
{
    doExecuteCommand($w{30}->getobj("commandline"), $w{30}->getobj("commandoutputlabel"));
}

sub doSetKeybindingsOfCommandLine
{
    my $text = shift;
    $text->set_binding( sub { cbExecuteCommand(); }, KEY_ENTER);
}

sub cbDbgBreak
{
    $g_gdb->interrupt();
    sleep(1);
    cbRefreshAllInfo();
    $g_gdb_state = 0;
    $w{30}->getobj("currentdbgstatus")->text("PAUZED");
}

sub cbDbgContinue
{
    sendCommand($g_gdb, "c", 1);
    $w{30}->getobj("currentdbgstatus")->text("RUNNING");
}

sub cbDbgStep
{
    $w{30}->getobj("currentdbgstatus")->text("STEPPING");
    my $res = sendCommand($g_gdb, "step", 1);
    if($res ne "TIMEOUT")
    {
        cbRefreshAllInfo();
        $w{30}->getobj("currentdbgstatus")->text("PAUZED");
    }
    else
    {
        doMessage("The step function is taking > 3 seconds, do this:\n" .
                "- Either break, the target may be running again\n" .
                "- Wait some more and the step operation may complete, then do a refresh\n");
    }
}

sub doQuit;

sub doInit
{
    $g_original_dir = trim(qx/pwd/);
    
    if($g_local_debug)
    {
        $g_executable = "./beertest/beer-process";
        $g_executable_dir = "./beertest";
        $g_executable_src_dir = "./beertest";
        $g_coredump_dir = "";
    }

    # $g_executable is always mandatory

    #optional param
    if(!defined($g_coredump_dir))
    {
        $g_coredump_dir = "";
    }

    #$g_executable_dir = ""; #filled in with parameters or auto-detect
    if(!defined($g_executable_dir)) #
    {
        $g_executable_dir = trim(qx/dirname $g_executable/);
        $g_executable = trim(qx/basename $g_executable/);
    }

    #$g_executable_src_dir = ""; #filled in with optional param
    if(!defined($g_executable_src_dir))
    {
        $g_executable_src_dir = "";
    }

    if(! -e $g_executable_dir)
    {
        printf "No directory of executable found: $g_executable_dir";
        doQuit(1);
        return;
    }

    chdir $g_executable_dir;

    if(! -e $g_executable)
    {
        printf "No executable found: $g_executable";
        doQuit();
        return;
    }

    if($g_attach_pid == 0) # debug a coredump file
    {
        $g_coredump = trim(qx/ls -1t $g_coredump_dir|head -n 1/);

        print $g_coredump;
        if($g_coredump  =~ m/.*No such file.*/)
        {
            dbg("No core dump file found!");
            doQuit();
            return;
        }
        $w{30}->getobj("currentdbgstatus")->text("COREDUMP");
        $g_gdb = newDebugger($g_executable, $g_coredump, $g_executable_src_dir);
    }
    else
    {
        $w{30}->getobj("currentdbgstatus")->text("PAUZED");
        $g_gdb = newDebugger($g_executable, $g_attach_pid, $g_executable_src_dir);
    }

    if(!defined($g_gdb))
    {
        printf "Error in creating new debugger! Exec=%s Core=%s", $g_executable, $g_coredump;
        doQuit();
        return;
    }

    doSetKeybindingsOfList($w{30}->getobj("threadlist"));
    doSetKeybindingsOfList($w{30}->getobj("stacklist"));
    doSetKeybindingsOfCommandLine($w{30}->getobj("commandline"));

    doRefreshThreadList();
}

sub doQuit
{
    my $exitcode = shift;
    if(!defined($exitcode))
    {
        $exitcode = 0;
    }
    if(defined($g_gdb))
    {
        if($g_attach_pid != 0)
        {
            cbDbgBreak;
            sendCommand($g_gdb, "detach");
        }
        deleteDebugger($g_gdb);
    }
    quit(0);
}

sub quit
{
    my $exit_code = shift;
    if(defined($g_original_dir) && length($g_original_dir) > 0)
    {
        chdir $g_original_dir;
    }
    exit($exit_code);
}

# ----------------------------------------------------------------------
# Setup bindings and focus
# ----------------------------------------------------------------------

# Bind <CTRL+Q> to quit.
$cui->set_binding( sub{ quit }, "\cQ" );

# Bind <CTRL+X> to menubar.
$cui->set_binding( sub{ shift()->root->focus('menu') }, "\cT" );

# init threads
doInit();

# Main menu
showMainmenu;

# ----------------------------------------------------------------------
# Get things rolling...
# ----------------------------------------------------------------------
$cui->mainloop();

doQuit;
